home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1993…ch: Other People's Memory / ADC Developer CD (1993-03) (''Other People's Memory'')_iso / Dev.CD Mar 93.iso / Technical Documentation / Sample Code / DTS.Lib & Samples / DTS.Lib / =Using TreeObj.c < prev    next >
Encoding:
Text File  |  1992-10-22  |  34.2 KB  |  601 lines  |  [TEXT/MPS ]

  1. ***** TreeObj.c usage documentation *****/
  2.  
  3. Purpose:  To simplify and standardize document manipulation, including undo.
  4.  
  5. Defining a generic document architecture that is flexible enough to handle the
  6. many and varied document types, yet simple enough to justify using it, can be
  7. difficult.
  8.  
  9. The document structure type chosen here is a simple hierarchy.  For simple document
  10. types, the hierarchical feature can simply be ignored.  All of the member objects of
  11. the document can simply be children of the root object.  This gives the document a
  12. linear feel, (all objects are at the same level in the hierarchy), which is all that
  13. is needed for a lot of applications.
  14.  
  15. For more complex document structures that have many data types which inter-relate, the
  16. hierarchical model is quite useful.  The sample given here is a draw program.  The draw
  17. example demonstrates both the linear document type (no grouping used) and the hierarchical 
  18. document type (grouping used).  If no grouping is used, then all of the objects added to
  19. the document are children of the root object.  The objects are arranged linearly off the
  20. root document object.  If some of the objects are grouped, a group object is first added
  21. to the root object, and then the selected objects are moved so that they are children of
  22. the group object.  Now the document is no longer linear.  The root object has children,
  23. some of which may be group objects.  These group objects in turn have children.
  24.  
  25. This sort of hierarchical support makes grouping of objects in a draw-type program trivial.
  26. It naturally represents the organization of the objects within the document.
  27.  
  28. An additional benefit of managing the objects in a document in such a standardized way is
  29. that additional application features such as undo/redo can be automatically supported.
  30. Whenever a change is to be made to the document, one of a few document-hierarchy-management
  31. calls is made first.  The different operations that can be done to the hierarchical document
  32. are:
  33.  
  34.     NewChild();
  35.     DisposeChild();
  36.     CopyChild();
  37.     MoveChild();
  38.     ModifyChild();
  39.     SwapChildren();
  40.  
  41. These six operations cover the various operations that can be performed to one or more of
  42. the document objects.  Children can be added, removed, copied, moved, modified, or swapped.
  43. If these calls are used to modify objects within the document hierarchy, then the editing
  44. operations done to the document can automatically be recorded for undo/redo purposes.
  45.  
  46. Let's look at a sample NewChild() call and its prototype:
  47.  
  48. TreeObjHndl    NewChild(short editType, TreeObjHndl parentHndl, short childNum, short ctype, long size);
  49.  
  50. NewChild(), if successful, returns a handle for the child object added to the document.
  51. The parameters are:
  52.     editType:    Application edit type for which this document modification is being done.
  53.     parentHndl:    The child created will be added as a child to this object.
  54.     childNum:    The child will be added to the parent as this child number.
  55.     ctype:        The child will be of this type.
  56.     size:        The child will be created this initial size (if size is greater than minimum).
  57.  
  58. NewChild() must have a parent declared.  This begs the question of where the initial parent
  59. comes from.  At some point, NewRootObj() must be called to create the initial root parent.
  60. As would be expected, the root object has no parent.  Once there is an initial root object,
  61. children can then be added to the document via NewChild().
  62.  
  63. The editType of the NewChild() call is used for automatically tracking undo/redo.  Many
  64. individual operations may be done to the document that are all for the same editing operation.
  65. Let's say that you have a word-processor application that has paragraph objects.  If your
  66. document has 27 paragraphs, then you have 27 children off the root object.  The order of the
  67. children is the order of the paragraphs in your document.
  68.  
  69. Let's now say that the user has selected some text.  The text selected starts in the middle of
  70. paragraph 3, goes through paragraphs 4-5, and through half of the text in paragraph 6.
  71. The user then deletes this text.  We may have declared this type of edit to be of type
  72. DELETE_EDIT.  The edits to the document that we will perform are:
  73.  
  74. 1) Modify paragraph 3 by removing the selected text and keeping the unselected text.
  75. 2) Delete paragraphs 4-5.
  76. 3) Modify paragraph 6 by removing the selected text and keeping the unselected text.
  77.  
  78. For step 1, we would first use ModifyChild().  The prototype for ModifyChild() is:
  79.  
  80.     OSErr    ModifyChild(short editType, TreeObjHndl phndl, short childNum, Boolean deepCopy);
  81.  
  82. ModifyChild() makes a copy of the object and places the copy in the undo hierarchy.  We are
  83. now free to modify the object any way we see fit.  If the user chooses to undo the change,
  84. the undo code simply swaps the data of the modified object with the copy that was saved in
  85. the undo hierarchy.  If the user later chooses to redo the edit, the undo code simply swaps
  86. the data again to perform the redo.
  87.  
  88. NOTE:  ModifyChild() may fail, since a copy of the object is made.  There may not be enough
  89. memory for this copy to be created.  If this is so, then a memFullErr will be returned.
  90.  
  91. The parameters are:
  92.     editType:    Application edit type for which this document modification is being done.
  93.     parentHndl:    Parent of the child being modified.
  94.     childNum:    Child number (of the parent) being modified.
  95.     deepCopy:    If true, the children of this child are also copied into the undo hierarchy.
  96.  
  97. The editType parameter is used to determine if this document modification is of the same
  98. edit type as previous document modifications.  If it is, then the operation is grouped
  99. with the other operations.  This is all done for the purposes of undo/redo.  Multiple objects
  100. may be modified in a single edit, and these modifications all have to be undone/redone with
  101. a single undo/redo.
  102.  
  103. If the editType is different than the last document modification, then a new undo group is
  104. started, and this operation is recorded in this new group.
  105.  
  106. For the above example, we would make the following call to ModifyChild() prior to removing
  107. the selected text from paragraph 3:
  108.  
  109.     err = ModifyChild(DELETE_EDIT, root, 3, false);
  110.  
  111. Of course, it would be a really odd application in which the '3' was hard-coded as in
  112. the above line.
  113.  
  114. Now paragraphs 4 and 5 need to be deleted.  This is done by using DisposeChild().
  115. The prototype for DisposeChild() is:
  116.     void    DisposeChild(short editType, TreeObjHndl parentHndl, short childNum);
  117.  
  118. Note that DisposeChild() will succeed, as it doesn't have to create a new handle for the
  119. copy as ModifyChild() does.  HOWEVER:  DisposeChild doesn't necessarily free up ram, as
  120. you would expect.  All that occurs is that the child is moved into the undo hierarchy so
  121. that undo can move the child back into the document.
  122.  
  123. To delete children 4 and 5, we would do the following:
  124.  
  125.     DisposeChild(DELETE_EDIT, root, 4);
  126.     DisposeChild(DELETE_EDIT, root, 4);
  127.  
  128. Note that for both calls we dispose of child number 4.  This is because once the first child
  129. is disposed of, what used to be child 5 is now child 4.
  130.  
  131. The last operation we would do is to declare our intent to change to the end child in the
  132. selection range.  This would be done as follows:
  133.     err = ModifyChild(DELETE_EDIT, root, 4, false);
  134.  
  135. The delete (assuming no errors) has now been completed.  Actually, the delete has occurred
  136. in either case.  If there were errors, all that occurred is that the undo information wasn't
  137. recorded.
  138.  
  139. Now let's say that the user selects a new range of text and deletes it, as well.  We would
  140. do the same thing as above to delete the text.  Here's the problem:  The editType of this
  141. second text delete is the same as the first.  Both document edits were of type DELETE_EDIT.
  142. This means that both operations were recorded under the same undo task.  When the user
  143. chooses undo, both deletes will be undone.  This isn't what we want.
  144.  
  145. To fix this problem, we have to make one additional call.  We need to call NewUndo().
  146. NewUndo() closes out the old editing task.  When you then make some document modification,
  147. it will be recorded in a separate undo task.
  148.  
  149. NewUndo() only takes a single parameter.  Just pass it any handle in the document.  Any
  150. object in the document can serve as a reference to the document.
  151.  
  152.  
  153. An object handle has 3 parts:
  154. 1) The header information.
  155. 2) The application-defined object data.
  156. 3) The child handle table.
  157.  
  158.  
  159. The object header is of the following form:
  160.  
  161. typedef struct TreeObj {
  162.     short        type;
  163.     short        numChildren;
  164.     long        dataSize;
  165.     long        treeID;
  166.     TreeObjHndl    parent;
  167. } TreeObj;
  168.  
  169. These fields are automatically filled out whenever an object is created via NewRootObj()
  170. or NewChild().
  171.  
  172. The application-defined data area is automatically initialized with 0's.  When NewRootObj()
  173. or NewChild() is called, a size parameter is passed in.  There is also a table of minimum sizes
  174. for each object type that needs to be defined.  If the size passed in is smaller than the
  175. minimum size, then the minimum size is used as the data size instead.
  176.  
  177. The minimum size table is a global array of longs called gMinTreeObjSize.  Its definition is
  178. found in file2.c.  For each type of object defined, there needs to be a minimum size defined.
  179.  
  180. There are some predefined object types.  These are defined by the following:
  181.  
  182. #define ROOTOBJ     1
  183. #define UNDOOBJ     2
  184. #define UNDOTASKOBJ 3
  185. #define UNDOPARTOBJ 4
  186.  
  187. These are the mandatory object types for supporting this implementation of the document
  188. structure and undo/redo features.
  189.  
  190. Application-defined object types can start with type number 16.  Type number 0 is reserved
  191. and is currently undefined.  Types 5-15 are reserved for future objects that may be added
  192. to the DTS.Lib application framework.
  193.  
  194. The numChildren field is initially 0 after calling either NewRootObj() or NewChild().
  195.  
  196. The dataSize is the effective data size after object creation, which is either the size passed
  197. in, or the minimum object size, whichever is greater.
  198.  
  199. The treeID field is initially set to 0 by both NewRootObj() and NewChild().  This field is
  200. used to uniquely identify members of the hierarchy by ID.  (More on this later.)
  201.  
  202. The parent field is a reference to the handle which is the parent.  As you would expect,
  203. objects created by NewRootObj() have no parent, and therefore this field is nil.
  204.  
  205.  
  206. The application-defined data area starts immediately after the header information.  As the
  207. structure of this portion is application-defined and variant, there is no structure pre-defined
  208. for it.  Each object type will have a unique structure definition for this portion of the object.
  209.  
  210.  
  211. The final part of an object is the child handle table.  The child handle table starts
  212. immediately after the data portion of the object.  The size of the header structure plus
  213. the value in the dataSize field serves as an offset from the beginning of the object to
  214. the child handle table.  Given the relationship between dataSize and the child handle
  215. table, it is important that the object isn't resized directly.  There are calls for managing
  216. the data area and its size that take the child handle table into account.  These are:
  217.  
  218.     OSErr    AdjustDataSize(TreeObjHndl hndl, long delta);
  219.     OSErr    SetDataSize(TreeObjHndl hndl, long newSize);
  220.     OSErr    SlideData(TreeObjHndl hndl, long offset, long delta);
  221.     void    *GetDataPtr(TreeObjHndl hndl);
  222.  
  223. AdjustDataSize() alters the size of the data area, based on the delta passed in.  If you wish
  224. to decrease the data area by one byte, for example, pass in a -1.  AdjustDataSize() does not
  225. reposition any of the data within the data area.  It does move the child handle table so that
  226. it continues to be positioned immediately after the data area.
  227.  
  228. SetDataSize() does as expected.  It specifically sets the data size to the designated
  229. size.  It also does not shift the data, but does do the appropriate child handle table
  230. maintenance, just as AdjustDataSize() does.
  231.  
  232. SlideData() operates the same as AdjustDataSize(), plus it also slides some or all of the
  233. data in the data area.  Use SlideData() to insert or remove data at some location within
  234. the data area.
  235.  
  236. GetDataPtr() simply returns a pointer to the beginning of the data area.  IT DOES NOT LOCK
  237. THE HANDLE!!  If the handle needs locking, then you must do this yourself.  Since the object
  238. is simply a handle, this poses no difficulties.  (Unlock when you are done, of course.)
  239.  
  240.  
  241. The child handle table is automatically handled.  There shouldn't be a reason that you need
  242. to manage or reference this yourself.  If however you end up needing to do this, there
  243. are calls to manage the child handle table.
  244.  
  245.  
  246. Objects referencing other objects:
  247.  
  248. Let's revisit the word-processor example.  Once again, we have this paragraph-based word
  249. processor.  The user selects some text.  The cursor location (insertion point) must be kept.
  250. A reference to the starting object and an offset into the data of that object would serve as
  251. the cursor location reference.
  252.  
  253. This reference to the paragraph object containing the insertion point could be kept as either
  254. a handle reference or a child number reference.  Either would serve the purpose.  Both would
  255. uniquely describe an object in the document.  The handle reference would directly reference
  256. the object.  The child number reference would indirectly reference the object.  If we used
  257. child numbers as our reference and we desired to get the handle of the object, we would do
  258. something like the following:
  259.  
  260.     chndl = GetChildHndl(root, cnum);
  261.  
  262. In our document example, all of the paragraph objects are children of the root object.
  263. In this case it would be a simple matter of getting the child handle.  The child number
  264. reference would probably be stored in the root object, as it is a global piece of information
  265. for this document.  We would probably want it saved with the document, which would occur
  266. automatically if it were stored in the root object.
  267.  
  268. So, since they both seem to work, which is better?  Is keeping the handle better, or is keeping
  269. the child number better?  When saving a document, child numbers will be meaningful when the
  270. document is opened at a future date.  References to handles aren't meaningful when saved to disk
  271. and then reloaded.  For this reason it seems that child number should win out.
  272.  
  273. But what if you wish to keep a reference to some arbitrary point in the document?  What if you
  274. don't know what the parent of that object is?  What if the object is at some arbitrary depth
  275. in the hierarchy?  In this case, it seems pretty clear that we wish to use handle references
  276. instead of child number references, at least when the document is in memory.
  277.  
  278. So what about handle references when saved to disk?  We need these references to persist.  To
  279. do this, we must convert them to something that saves meaningfully and can be converted back
  280. to handle references when the document is reopened.
  281.  
  282. This is what the treeID field in the object header is for.  When DoNumberTree() is called,
  283. all of the objects in the hierarchy are uniquely numbered.  DoNumberTree() is automatically
  284. called by the shell just prior to the objects in a hierarchy are written to disk.  In
  285. addition, prior to writing an object out to disk, the object is called with a message
  286. requesting it to convert any handle references it contains to treeID references.  For each
  287. handle reference that needs to be converted, you need to call Hndl2ID() to do the conversion.
  288. Hndl2ID() depends on DoNumberTree() already being called so that all of the treeID fields
  289. for all of the objects in the hierarchy are current.
  290.  
  291. After calling the object with the handle-to-id conversion message, the object is written
  292. to disk.  Once it is written, a message is sent to the object requesting it to covert the
  293. id back into a handle reference.  For each reference converted, you need to call ID2Hndl()
  294. to deconvert the reference back into a handle.
  295.  
  296. When a document is opened, all of the objects are first read into memory.  Once the entire
  297. document is in memory, DoNumberTree() is called, and then each object in the document is
  298. sent a message requesting it to convert the converted handle references back into real
  299. handle references.
  300.  
  301. The reason that the entire document must first be read is that the reference to another object
  302. may be to an object that is later in the document.  Only after the entire document is read in
  303. is it possible to resolve all references to anywhere in the document.
  304.  
  305. This messaging mechanism allows you to use handle references within your application without
  306. worrying about them persisting through a save/open cycle.  So, given the above messaging
  307. mechanism, using either child number references or handle references is equally valid.
  308. Whatever seems easiest for a particular application is the one to use.
  309.  
  310.  
  311. There is a standard set of messages passed to objects.  The above message is just one of these.
  312. The standard list of messages (and possible sub-messages) is defined as below:
  313.  
  314. #define INITMESSAGE      0        /* Additional object initialization. */
  315. #define     CREATEINIT       0        /* Object being initially created. */
  316. #define     WINDOWINIT       1        /* Document/object being assigned to a window. */
  317. #define     NOWINDOWINIT     2        /* Document/object being freed from a window. */
  318.  
  319. #define FREEMESSAGE      1        /* Additional object disposal. */
  320.  
  321. #define COPYMESSAGE      2        /* Additional object cloning. */
  322.  
  323. #define UNDOMESSAGE      3        /* Additional object undo. */
  324. #define     UNDOFROMDOC      0        /* Object leaving document. */
  325. #define     UNDOTODOC        1        /* Object returning to document. */
  326.  
  327. #define CONVERTMESSAGE   4        /* Hndl2ID or ID2Hndl conversions. */
  328. #define     CONVERTTOID      0        /* Convert handle references to ID references. */
  329. #define     CONVERTTOHNDL    1        /* Convert ID references to handle references. */
  330.  
  331. #define FREADMESSAGE     5        /* Read object data from file. */
  332. #define FWRITEMESSAGE    6        /* Write object data to file. */
  333.  
  334. #define HREADMESSAGE     7        /* Read object data from handle. */
  335. #define HWRITEMESSAGE    8        /* Write object data to handle. */
  336.  
  337. #define HITTESTMESSAGE   9        /* Test if object was hit. */
  338. #define     HITTESTOBJ       0        /* Test body of object for hit. */
  339. #define     HITTESTGRABBER   1        /* Test sizing parts for hit. */
  340. #define     CANBETARGET      2        /* Return true if object can become target. */
  341. #define     CANTAKEKEYS      3        /* Return true if target can take keys. */
  342.  
  343. #define GETRGNMESSAGE    10        /* Return object bounding box. */
  344. #define GETBBOXMESSAGE   11        /* Return object bounding box. */
  345. #define SETBBOXMESSAGE   12        /* Set object bounding box. */
  346. #define SECTBBOXMESSAGE  13        /* Check if rect intersects object bounding box. */
  347.  
  348. #define TARGETMESSAGE    14        /* Set target status for object. */
  349. #define     TARGETOFF        0        /* Make the object no longer the target. */
  350. #define     TARGETON         1        /* Make the object the target. */
  351.  
  352. #define DRAWMESSAGE      15        /* Draw some form of the object. */
  353. #define     DRAWOBJ          0        /* Draw the object. */
  354. #define     ERASEOBJ         1        /* Draw the object. */
  355. #define     DRAWSELECT       2        /* Draw the selection portion of the object. */
  356. #define     DRAWGHOST        3        /* Draw an xor-ghost (for dragging) of the image. */
  357. #define     DRAWMASK         4        /* Draw a mask of the image (for offscreen layer masking.) */
  358.  
  359. #define PRINTMESSAGE     16        /* Print the object. */
  360.  
  361. #define VHMESSAGE        17        /* Format View Hierarchy information for the object. */
  362.  
  363.  
  364. INITMESSAGE:  This is called with a sub-message of what kind of init it is.  The CREATEINIT
  365. sub-message  indicates that the object is initially being created.  This sub-message is in
  366. case there is additional data initialization that must occur.  Depending on the object, there
  367. may be handles that need to be created off the object itself.  It may not be a simple linear
  368. block of data.  In these cases, you want to do this additional initialization when the
  369. INITMESSAGE is received.
  370. The WINDOWINIT sub-message is in case certain objects have a different state when the document
  371. has a window than when it doesn't.  For example:  If there were a TextEdit object, then when
  372. the file is initially read in and the CREATEINIT sub-message is passed to the object, there is
  373. no window to pass to TENew() to create a new TextEdit record.  The data has to be read in as
  374. regular text that has no TERecord yet.  Once the document is assigned a window, a TERecord can
  375. be created for the object and the text data can be moved into the TERecord.  This additional
  376. WINDOWINIT sub-message is defined for just such objects that you may create.  Note that
  377. nowhere in the application framework is there a call that passes a WINDOWINIT sub-message.
  378. This would be an application-specific function, and therefore this call would belong in the
  379. application.  The most likely place for this would be in the InitContent() function for the
  380. application.  You would do something like the following:
  381.     DoFTreeMethod(root, INITMESSAGE, WINDOWINIT);
  382. This would pass each object in the document an INITMESSAGE with a sub-message of WINDOWINIT.
  383. The final sub-message is NOWINDOWINIT.  This message occurs when a document is being detached
  384. from a window.  If an object has to change state to accomodate being related to a window, then
  385. it would need to change state back when disassociated with that window.
  386.  
  387. FREEMESSAGE:  This is called due to DisposeChild() being called.  DisposeChild() will
  388. dispose of simple objects that don't have additional handles of data.  Any handles that were
  389. created at INITMESSAGE time should be disposed of when the FREEMESSAGE is received.
  390.  
  391. COPYMESSAGE:  When CopyChild() is called, a new child is created via calling NewChild().
  392. Normally when NewChild() is called, the object is called with INITMESSAGE.  However,
  393. when a child is being copied, the data area is copied into the copy child.  This data
  394. copy would clobber any handle references the newly created copy would have, thus orphaning
  395. those handles.  For this reason, no INITMESSAGE was passed to the object by NewChild().
  396. The data was copied into the copy however, so any handle references in the copy aren't unique
  397. references.  The original child has the same references.  This isn't a good situation.
  398. The first thing that the code for the COPYMESSAGE needs to do is to send itself an
  399. INITMESSAGE so that unique handles for the copy are created.  Once this is done, then
  400. the code for COPYMESSAGE can actually copy the data in these handles.  Once this final
  401. copying is done, we have a complete and separate copy of the original child.
  402.  
  403. UNDOMESSAGE:  When the user performs an undo or redo, involved objects get passed
  404. this message.  When an undo/redo occurs objects are either moving into or out of the
  405. document.  Objects move back and forth between the document and the undo hierarchy.
  406. The sub-messages UNDOFROMDOC and UNDOTODOC determine the direction.  There may be various
  407. tasks that need to be performed to this object and other parts of the document when an
  408. undo/redo occurs.  When an undo of a DELETE_EDIT is performed, it is typical to select
  409. the portion of the document undeleted.  The root object may hold the selection information.
  410. If this is the case, the object should call GetRootHndl() and then adjust the selection
  411. accordingly.  Note that the root object may turn out to be the undo hierarchy root object.
  412. If the object has been moved out of the document and into the undo hierarchy, this will
  413. turn out to be the case.  In this instance nothing would have to be done.  At least one
  414. message will be sent to the object while it is still in the document.  The sub-message
  415. will then state whether the document is about to leave or just was added to the document.
  416. Let's say that the root object contains a count of the number of selected items.  In this
  417. case, if the item is selected, and it is about to leave the document, then the count of
  418. items selected will need to be decremented.  If the item has just moved into the document,
  419. then the item needs to be set as selected, plus the number of selected items count needs
  420. to be increased by 1.
  421. An additional call is made prior to any objects being moved.  This is to globally prepare
  422. the document for an undo/redo operation.  Given that the items involved in the undo/redo
  423. should be selected, this suggests that other selected items should be deselected prior
  424. to the undo/redo.  This global undo/redo setup call is the place to do things like this.
  425. Also, once the undo/redo operation is complete and all objects involved in the operation
  426. are moved and messaged, a final call is made to clean up any unfinished undo/redo business.
  427. To recap the undo/redo procedure:
  428.     1) A global get-ready-to-undo/redo call is made.  This function is called UndoFixup.
  429.        It is passed a reference to the document, plus a sub-message stating that it is
  430.        called for pre-undo/redo tasks or post-undo/redo tasks (in this case pre-undo/redo).
  431.     2) Each object involved in the undo/redo task is called with appropriate messages
  432.        stating whether it is leaving or entering the document.  Appropriate document
  433.        maintenance tasks should be performed based on these messages/sub-messages.
  434.     1) UndoFixup is called a final time.  Once again, it is passed a reference to the document,
  435.        plus a sub-message stating that it is called for post-undo/redo tasks.
  436.  
  437. CONVERTMESSAGE:  This is called to convert handle references within an object to treeID
  438. values and visa versa, depending on the sub-message.
  439. For the sub-message CONVERTTOID:  Prior to the first object being written to disk, DoNumberTree()
  440. is called to assign a unique treeID to each object in the document.  For each handle reference
  441. in an object, call Hndl2ID() to convert it to a treeID value.  Once the object is written to
  442. disk, the object will be called again with the sub-message CONVERTTOHNDL.  This indicates that
  443. the handle references that were converted to treeID values should be converted back.  Call
  444. ID2Hndl() to do the reverse conversion.
  445. For the sub-message CONVERTTOHNDL:  The entire document is in memory prior to ever receiving this
  446. message.  In the case of writing a document to disk, the document is already in memory.  For
  447. the case where a document is being opened, the entire document is first read in, and then
  448. objects are passed this message as an opportunity to convert treeID values into handle references.
  449. In either case, DoNumberTree() will have already been called, so it is okay to call ID2Hndl().
  450.  
  451. FREADMESSAGE:  This is called to read in the data portion of an object.  The header
  452. information has already been read in.  Since the header information doesn't vary according
  453. to the object type, it can be read in generically.  Also, the header information states what
  454. type the object is, so until it the header is read in, the object type can't be determined.
  455. If the data doesn't have any additional handle references, just call the default function
  456. to read in the data.  The default function is called ReadTreeObjData().  It will read in
  457. the number of bytes designated by the dataSize in the header, which has already been read in.
  458. If there is additional data for the object to be kept in handles, or some such other unique
  459. situation, the code to do this goes here.
  460.  
  461. FWRITEMESSAGE:  This is called to write out the data portion of an object.  The header
  462. information has already been written.  Since the header information doesn't vary according
  463. to the object type, it can be written generically.  If the data doesn't have any additional
  464. handle references, just call the default function to write out the data.  The default
  465. function is called WriteTreeObjData().  It will write out the number of bytes designated by
  466. the dataSize in the header.  If there is additional data for the object kept in handles, or
  467. some such other unique situation, the code to write this additional data goes here.
  468.  
  469. HREADMESSAGE:  This message is similar to FREADMESSAGE, except that the data is read from
  470. a handle instead of from disk.  The assumption is that the data is already in a handle.
  471. A pointer to a structure containing the handle and the current offset into the handle is
  472. passed in.  HREADMESSAGE reads in the data from the handle into the object, and then
  473. moves the offset to point past the data.  The reasoning behind this particular message
  474. is that this will allow initial document data to be read from a resource.  The resource
  475. would contain the initialization information for the document.  In ths way a document type
  476. could be prototyped easily so that it would contain the initial objects.  Once the new
  477. document is in ram, it could change, due to user editing.  This is fine because the objects
  478. will be saved with the document, including the initial objects.  The resource would only be
  479. used for new documents.
  480.  
  481. HWRITEMESSAGE:  This message is similar to FWRITEMESSAGE, except that it is used to write
  482. the data into a handle.  A pointer to a structure containing the handle and the current
  483. offset into the handle is passed in.  This message allows the creation of a handle that can
  484. be stored as a resource for when a new document is created.
  485.  
  486. HITTESTMESSAGE:  This message is used for hit-testing of an object, along with various
  487. targeting and keystroke information for the object.  See the DTS.Draw example for
  488. implementation details.
  489.  
  490. GETRGNMESSAGE:  This message is requesting that the object return a region that describes
  491. its shape.
  492.  
  493. GETBBOXMESSAGE:  This message is requesting that the object return a rectangle that encloses
  494. all portions of the object.
  495.  
  496. SETBBOXMESSAGE:  This message is used to change the size of the bounding rectangle for
  497. an object.
  498.  
  499. SECTBBOXMESSAGE:  This message is used to determine if the object's bounding rectangle
  500. intersects the given rectangle.
  501.  
  502. TARGETMESSAGE:  This message is how an object is made the target or not the target of keystrokes.
  503.  
  504. DRAWMESSAGE:  This message and sub-message is to tell the object to draw itself, and in
  505. what form.
  506.  
  507. PRINTMESSAGE:  This message is to tell the object to print itself.  Assumably the data
  508. field will vary, according to the object type.  Objects often print themselves differently
  509. than they draw, so specific object information will have to be passed in for these cases.
  510.  
  511. VHMESSAGE:  This message allows the object to format the data information that is viewed
  512. when using the View Hierarchy object viewing feature.
  513.  
  514.  
  515. As is evident from the above descriptions, the behaviors for the different types of objects
  516. is completely dependent on what is done for the various messages.  To define an object type,
  517. you need to make an entry into two tables.  These tables are:
  518.     1) gTreeObjMethods
  519.     2) gMinTreeObjSize
  520. These tables are found in file2.c.  As you add objects to your application, just make entries
  521. for these new objects into these tables.
  522. If you want the default behaviors for an object, then use nil for the method procedure.  If
  523. the method procedure is nil, then the object is passed no messages, as it is assumed that it
  524. is simple and generic enough to be handled automatically.  If you need to handle just one
  525. message specifically , you will then need to define a method procedure.  With method procedures,
  526. it is an all-or-nothing situation.  If you set the method procedure to non-nil for an object,
  527. then that object will receive all messages.  For these instances you can just call the default
  528. code directly, such as ReadTreeObjData() and WriteTreeObjData() for handling file I/O for
  529. the object.
  530.  
  531.  
  532. Since the object is a handle of three components, referencing the object as if it is unique
  533. can be a bit of a pain.  The unique portion of the object, the data portion, is actually in
  534. the middle of the object.  To syntactically fix this problem, you will want to define some
  535. macros for dereferencing into an object with appropriate object-type typecasting.  In
  536. FileFormat.h you will find some such dereferencing macros.  They are in the form:
  537.  
  538. #define mDerefRoot(hndl)     ((RootObj*)(((char*)(*(hndl)))+sizeof(TreeObj)))
  539.  
  540. This macro allows you to write code that looks like the following:
  541.  
  542.     numSelected = mDerefRoot(root)->numSelected;
  543.  
  544. The only danger with this is that the handle that is dereferenced doesn't have to be of type
  545. RootObj.  It can be of any type.  This is convenient and not.  It allows objects of different
  546. types to be treated similarly or differently, whichever the code demands.
  547.  
  548.  
  549. There are two defines in FileFormat.h that I should mention.  These are:
  550. 1) MAXNUMUNDOS
  551. 2) NUMSAVEUNDOS
  552.  
  553. These govern the depth of the undo mechanism.
  554.  
  555. MAXNUMUNDOS is the maximum number of undos that will be recorded before the oldest are
  556. automatically purged.  This can be set up to 65535, if you so wish, although that will
  557. cause a lot of undo hierarchy object to be kept around and is more than any human user
  558. can comprehend.
  559.  
  560. NUMSAVEUNDOS is the maximum number of undos that are saved along with the document.  If this
  561. number if non-zero, then when the document is opened, the user may already have some undos
  562. that can be performed from the last editing session.  Setting this constant to zero makes
  563. the application save no undos along with the document.
  564.  
  565.  
  566. The final thing I will cover here is the 'View Hierarchy' facility in this sample.  As
  567. powerful as a hierarchical document structure can be, it can also be difficult to examine
  568. and debug.  The debugging feature of a 'View Hierarchy' window has been introduced to
  569. address this problem.
  570.  
  571. Whatever window is the top-most window when 'View Hierarchy' is selected will be referenced
  572. by the 'View Hierarchy' window.  The 'View Hierarchy' window will be given the same window
  573. name as the window being referenced to keep confusion to a minimum if multiple 'View Hierarchy'
  574. windows are opened at once.
  575.  
  576. The TextEdit control on the left is used to display the data in the object.  The small TextEdit
  577. control near the top-right is used to enter an alternate object handle to display.  The List
  578. control on the left displays the parent hierarchy.  The List control on the right is used
  579. to display the child handle table of the currently displayed object.  By double-clicking on
  580. members of these two lists, you can navigate the document hierarchy.
  581.  
  582. The data TextEdit control displays the header information of the object, plus the data portion
  583. of the object.
  584.  
  585. For root objects, the first 4 bytes of the data area hold a reference to the undo hierarchy of
  586. the document.  To view the undo hierarchy, either enter this handle in the small TextEdit
  587. control and press display, or just select the text for the handle in the data display area and
  588. press display.  Either method will make the undo root object the object to display.
  589.  
  590. The undo root object holds the document root in the first 4 bytes of the data area, so to
  591. go back to displaying the document root, just do the same as you did to display the undo root
  592. object.
  593.  
  594. If you try to display a handle that doesn't exist, the 'View Hierarchy' code will beep at you
  595. and do nothing.  If you enter a handle that exists, but isn't an object handle, it will try
  596. to display something and probably crash.  (Remember, this is only meant as a debugging aid.)
  597.  
  598.  
  599. This is probably enough for an introduction.  Have fun.
  600.  
  601.